package com.katesoft.scale4j.web.support;

import static com.katesoft.scale4j.common.services.IBeanNameReferences.HIBERNATE_TEMPLATE;
import static com.katesoft.scale4j.common.services.IBeanNameReferences.TRANSACTION_TEMPLATE;
import static com.katesoft.scale4j.web.model.Role.PROP_ROLE;
import static com.katesoft.scale4j.web.security.ISecurityRoles.ROLE_SYSTEM_ADMIN;
import static java.util.Arrays.asList;
import static org.apache.commons.collections.CollectionUtils.union;
import static org.perf4j.helpers.MiscUtils.splitAndTrim;

import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;

import org.apache.commons.codec.digest.DigestUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import com.katesoft.scale4j.web.model.Role;
import com.katesoft.scale4j.web.model.User;

/**
 * This class can be used for storing supported {@link Role}.
 * 
 * @author kate2007
 */
public class DefaultDataLoaderListener implements ApplicationListener<ContextRefreshedEvent> {
   private String adminUsername;
   private String adminPassword;
   private Collection<String> roles = new LinkedHashSet<String>();

   @Override
   public void onApplicationEvent(final ContextRefreshedEvent event) {
      event.getApplicationContext().getBean(TRANSACTION_TEMPLATE, TransactionTemplate.class)
               .execute(new TransactionCallback<Object>() {
                  @Override
                  public Object doInTransaction(@SuppressWarnings("unused") TransactionStatus status) {
                     event.getApplicationContext()
                              .getBean(HIBERNATE_TEMPLATE, HibernateTemplate.class)
                              .execute(new HibernateCallback<Object>() {
                                 @SuppressWarnings({ "unchecked" })
                                 @Override
                                 public Object doInHibernate(Session session)
                                          throws HibernateException, SQLException {
                                    Role adminRole = null;
                                    Criteria criteria;
                                    Collection<String> supportedRoles = union(
                                             Role.supportedRoles(), roles);
                                    for (final String role : supportedRoles) {
                                       criteria = session.createCriteria(Role.class);
                                       criteria.add(Restrictions.eq(PROP_ROLE, role));
                                       Role persistentRole = (Role) criteria.uniqueResult();
                                       if (persistentRole == null) {
                                          persistentRole = new Role();
                                          persistentRole.setRole(role);
                                          persistentRole.setDescription(String
                                                   .format("this description was generated by system for role [%s]",
                                                            role));
                                          session.save(persistentRole);
                                          persistentRole.getUniqueIdentifier();
                                       }
                                       if (persistentRole.getRole().equalsIgnoreCase(
                                                ROLE_SYSTEM_ADMIN)) {
                                          adminRole = persistentRole;
                                       }
                                    }
                                    criteria = session.createCriteria(User.class);
                                    criteria.add(Restrictions.eq(User.PROP_LOGIN, adminUsername));
                                    User persistentUser = (User) criteria.uniqueResult();
                                    if (persistentUser == null) {
                                       persistentUser = new User();
                                       persistentUser.setLogin(adminUsername);
                                       persistentUser.setPassword(DigestUtils.shaHex(adminPassword));
                                       persistentUser.setRoles(Collections.singleton(adminRole));
                                       persistentUser.getAccount();
                                       persistentUser.getPerson();
                                       session.save(persistentUser);
                                       persistentUser.getUniqueIdentifier();
                                    }
                                    return null;
                                 }
                              });
                     return null;
                  }
               });
   }

   /**
    * typically there is admin user in application. This method allows to specify admin's username.
    * 
    * @param adminUsername
    *           login of admin user.
    */
   @Required
   public void setAdminUsername(String adminUsername) {
      this.adminUsername = adminUsername;
   }

   /**
    * typically there is admin user in application. This method allows to specify admin's password.
    * 
    * @param adminPassword
    *           login of admin user.
    */
   @Required
   public void setAdminPassword(String adminPassword) {
      this.adminPassword = adminPassword;
   }

   /**
    * inject additional roles to be persisted into database.
    * 
    * @param roles
    *           comma separated
    */
   public void setRoles(String roles) {
      this.roles.addAll(asList(splitAndTrim(roles, ",")));
   }

   /**
    * inject collection of additional roles to be persisted.
    * 
    * @param roles
    *           collection of roles.
    */
   public void setRoles(Collection<String> roles) {
      this.roles = roles;
   }
}
